/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is Forte for Java, Community Edition. The Initial * Developer of the Original Code is Sun Microsystems, Inc. Portions * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved. */ package org.netbeans.editor; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.awt.event.InputEvent; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import java.util.Hashtable; import java.util.Map; import java.util.List; import java.util.ArrayList; import java.util.Iterator; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeSupport; import javax.swing.Action; import javax.swing.JComponent; import javax.swing.JViewport; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.SwingUtilities; import javax.swing.JPopupMenu; import javax.swing.KeyStroke; import javax.swing.event.ChangeListener; import javax.swing.event.ChangeEvent; import javax.swing.text.JTextComponent; import javax.swing.text.Caret; import javax.swing.text.BadLocationException; import javax.swing.text.EditorKit; /** * Extended UI for the component. All the additional UI features * like advanced scrolling, info about fonts, abbreviations, * keyword matching are based on this class. * * @author Miloslav Metelka * @version 1.00 */ public class ExtUI implements ChangeListener, PropertyChangeListener, SettingsChangeListener { public static final String OVERWRITE_MODE_PROPERTY = "overwriteMode"; // NOI18N public static final String COMPONENT_PROPERTY = "component"; // NOI18N public static final String POPUP_MENU_PROPERTY = "popupMenu"; // NOI18N /** Default scrolling type is used for the standard * setDot() call. If the area is on the screen, it * jumps to it, otherwise it centers the requested area * vertically in the middle of the window and it uses * smallest covering on the right side. */ public static final int SCROLL_DEFAULT = 0; /** Scrolling type used for regular caret moves. * The scrollJump is used when the caret requests area outside the screen. */ public static final int SCROLL_MOVE = 1; /** Scrolling type where the smallest covering * for the requested rectangle is used. It's useful * for going to the end of the line for example. */ public static final int SCROLL_SMALLEST = 2; /** Scrolling type for find operations, that can * request additional configurable area in each * direction, so the context around is visible too. */ public static final int SCROLL_FIND = 3; private static final Insets NULL_INSETS = new Insets(0, 0, 0, 0); private static final Dimension NULL_DIMENSION = new Dimension(0, 0); private static final int STYLE_CNT = 4; /** Component this extended UI is related to. */ private JTextComponent component; private JComponent extComponent; /** ID of the component in registry */ int componentID = -1; /** Property change support for firing property changes */ PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this); /** Document for the case ext ui is constructed without the component */ private BaseDocument printDoc; /** Draw layer chain */ DrawLayerList drawLayerList = new DrawLayerList(); /** Map holding the [name, coloring] pairs */ Map coloringMap; /** Character (or better line) height. Particular view can use a different * character height however most views will probably use this one. */ int charHeight = 1; // prevent possible division by zero float lineHeightCorrection = 1.0f; /** Width of the space characters in all font style combinations. */ int[] spaceWidths = new int[STYLE_CNT]; /** Space width for default component font */ int defaultSpaceWidth; /** Width of the digit for line numbering */ int lineNumberDigitWidth; /** Ascent size of the font. This is useful for drawing the text */ int[] ascents = new int[STYLE_CNT]; /** Ascent of line numbers */ int lineNumberAscent; /** Flag to initialize fonts */ private boolean fontsInited; /** Have the font characters fixed width? It means whether the characters * of one of the font styles have the same width. */ boolean fixedFont; /** Have the font characters of all font styles the same width? If this * flag is true then the fixedFont flag is also true. */ boolean superFixedFont; /** Should the search words be colored? */ boolean highlightSearch; /** Enable displaying line numbers. Both this flag and <tt>lineNumberVisibleSetting</tt> * must be true to have the line numbers visible in the window. This flag is false * by default. It's turned on automatically if the getExtComponent is called. */ boolean lineNumberEnabled; /** This flag corresponds to the LINE_NUMBER_VISIBLE setting. */ boolean lineNumberVisibleSetting; /** Whether to show line numbers or not. This flag is obtained using bitwise AND * operation on lineNumberEnabled flag and lineNumberVisibleSetting flag. */ boolean lineNumberVisible; int baseLeftMargin; /** Line number total width with indentation */ int lineNumberWidth; /** Current maximum count of digits in line number */ int lineNumberMaxDigitCnt; /** Margin on the left side of the line number */ Insets lineNumberMargin; /** This is the size of the editor as component while the real size * of the lines edited can be lower. The reason why to use this * virtual size is that each resizing of the component means * revalidating and therefore repainting of the whole component. */ Rectangle virtualSize = new Rectangle(); /** This is the increment by which the size of the component * is increased. */ // Rectangle virtualSizeIncrement = new Rectangle(); !!! Insets textMargin = NULL_INSETS; /** How much columns/lines to add when the scroll is performed * so that the component is not scrolled so often. * Negative number means portion of the extent width/height */ Insets scrollJumpInsets; /** How much columns/lines to add when the scroll is performed * so that the component is not scrolled so often. * Negative number means portion of the extent width/height */ Insets scrollFindInsets; /** Flag saying whether either the width or height in virtualSize * were updated. */ boolean virtualSizeUpdated; /** Listener to changes in settings */ private PropertyChangeListener settingsListener; /** ExtUI properties * @associates Object*/ Hashtable props = new Hashtable(11); boolean textLimitLineVisible; Color textLimitLineColor; int textLimitWidth; private Rectangle lastExtentBounds = new Rectangle(); private Dimension componentSizeIncrement = new Dimension(); private Abbrev abbrev; private WordMatch wordMatch; private ToolTipSupport toolTipSupport; /** Status bar */ StatusBar statusBar; private FocusAdapter focusL; Map renderingHints; /** Construct extended UI for the use with a text component */ public ExtUI() { Settings.addSettingsChangeListener(this); focusL = new FocusAdapter() { public void focusGained(FocusEvent evt) { Registry.activate(getComponent()); } }; } /** Construct extended UI for printing the given document */ public ExtUI(BaseDocument printDoc) { this.printDoc = printDoc; settingsChange(null); fixedFont = true; superFixedFont = true; for (int i = 0; i < spaceWidths.length; i++) { spaceWidths[i] = 1; ascents[i] = 0; } updateLineNumberWidth(); addLayer(new DrawLayerFactory.SyntaxLayer()); } /** Called when the <tt>BaseTextUI</tt> is being installed * into the component. */ protected void installUI(JTextComponent c) { this.component = c; // listen on component component.addPropertyChangeListener(this); component.addFocusListener(focusL); // listen on caret Caret caret = component.getCaret(); if (caret != null) { caret.addChangeListener(this); } BaseDocument doc = getDocument(); if (doc != null) { modelChanged(null, doc); } getToolTipSupport(); // cause tooltip support initialization settingsChange(null); putProperty(COMPONENT_PROPERTY, c); } /** Called when the <tt>BaseTextUI</tt> is being uninstalled * from the component. */ protected void uninstallUI(JTextComponent c) { // stop listening on caret Caret caret = component.getCaret(); if (caret != null) { caret.removeChangeListener(this); } // stop listening on component component.removePropertyChangeListener(this); component.removeFocusListener(focusL); BaseDocument doc = getDocument(); if (doc != null) { modelChanged(doc, null); } component = null; putProperty(COMPONENT_PROPERTY, null); } public void addPropertyChangeListener(PropertyChangeListener l) { propertyChangeSupport.addPropertyChangeListener(l); } public void addPropertyChangeListener(String propertyName, PropertyChangeListener l) { propertyChangeSupport.addPropertyChangeListener(propertyName, l); } public void removePropertyChangeListener(PropertyChangeListener l) { propertyChangeSupport.removePropertyChangeListener(l); } public void removePropertyChangeListener(String propertyName, PropertyChangeListener l) { propertyChangeSupport.removePropertyChangeListener(propertyName, l); } protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) { propertyChangeSupport.firePropertyChange(propertyName, oldValue, newValue); } public void settingsChange(SettingsChangeEvent evt) { if (component != null) { if (Utilities.getKit(component) == null) { return; // prevent problems if not garbage collected and settings changed } } Class kitClass = getKitClass(); String settingName = (evt != null) ? evt.getSettingName() : null; if (settingName == null || Settings.LINE_NUMBER_VISIBLE.equals(settingName) || Settings.PRINT_LINE_NUMBER_VISIBLE.equals(settingName) ) { lineNumberVisibleSetting = SettingsUtil.getBoolean(kitClass, (component != null) ? Settings.LINE_NUMBER_VISIBLE : Settings.PRINT_LINE_NUMBER_VISIBLE, false); lineNumberVisible = lineNumberEnabled && lineNumberVisibleSetting; } BaseDocument doc = getDocument(); if (doc != null) { if (settingName == null || settingName.endsWith(Settings.COLORING_NAME_SUFFIX) || settingName.endsWith(Settings.COLORING_NAME_PRINT_SUFFIX) ) { coloringMap = null; // reset coloring map so it's lazily rebuilt } if (settingName == null || Settings.LINE_NUMBER_MARGIN.equals(settingName)) { Insets m = (Insets)Settings.getValue(kitClass, Settings.LINE_NUMBER_MARGIN); lineNumberMargin = (m != null) ? m : NULL_INSETS; } if (settingName == null || Settings.LINE_HEIGHT_CORRECTION.equals(settingName)) { Float f = (Float)Settings.getValue(kitClass, Settings.LINE_HEIGHT_CORRECTION); lineHeightCorrection = (f != null) ? f.floatValue() : 1.0f; } if (settingName == null || Settings.TEXT_LIMIT_LINE_VISIBLE.equals(settingName)) { textLimitLineVisible = SettingsUtil.getBoolean(kitClass, Settings.TEXT_LIMIT_LINE_VISIBLE, false); } if (settingName == null || Settings.TEXT_LIMIT_LINE_COLOR.equals(settingName)) { textLimitLineColor = (Color)Settings.getValue(kitClass, Settings.TEXT_LIMIT_LINE_COLOR); } if (settingName == null || Settings.TEXT_LIMIT_WIDTH.equals(settingName)) { textLimitWidth = SettingsUtil.getInteger(kitClass, Settings.TEXT_LIMIT_WIDTH, DefaultSettings.defaultTextLimitWidth); } // component only properties if (component != null) { if (settingName == null || Settings.SCROLL_JUMP_INSETS.equals(settingName)) { scrollJumpInsets = (Insets)Settings.getValue(kitClass, Settings.SCROLL_JUMP_INSETS); if (scrollJumpInsets == null) { scrollJumpInsets = NULL_INSETS; } } if (settingName == null || Settings.SCROLL_FIND_INSETS.equals(settingName)) { scrollFindInsets = (Insets)Settings.getValue(kitClass, Settings.SCROLL_FIND_INSETS); if (scrollFindInsets == null) { scrollFindInsets = NULL_INSETS; } } if (settingName == null || Settings.COMPONENT_SIZE_INCREMENT.equals(settingName)) { componentSizeIncrement = (Dimension)Settings.getValue(kitClass, Settings.COMPONENT_SIZE_INCREMENT); if (componentSizeIncrement == null) { componentSizeIncrement = NULL_DIMENSION; } } if (settingName == null || Settings.RENDERING_HINTS.equals(settingName)) { renderingHints = (Map)Settings.getValue(kitClass, Settings.RENDERING_HINTS); } if (settingName == null || Settings.CARET_COLOR_INSERT_MODE.equals(settingName) || Settings.CARET_COLOR_OVERWRITE_MODE.equals(settingName) ) { Boolean b = (Boolean)getProperty(OVERWRITE_MODE_PROPERTY); Color caretColor; if (b == null || !b.booleanValue()) { caretColor = (Color)Settings.getValue(kitClass, Settings.CARET_COLOR_INSERT_MODE); } else { caretColor = (Color)Settings.getValue(kitClass, Settings.CARET_COLOR_OVERWRITE_MODE); } if (caretColor != null) { component.setCaretColor(caretColor); } } component.setKeymap(Utilities.getKit(component).getKeymap()); fontsInited = false; BaseTextUI ui = (BaseTextUI)component.getUI(); ui.updateHeight(); component.repaint(); } } } public void stateChanged(final ChangeEvent e) { SwingUtilities.invokeLater( new Runnable() { public void run() { BaseKit kit = Utilities.getKit(component); if (kit != null) { boolean selectionVisible = ((Caret)e.getSource()).isSelectionVisible(); Action a = kit.getActionByName(BaseKit.cutAction); if (a != null) { a.setEnabled(selectionVisible); } a = kit.getActionByName(BaseKit.copyAction); if (a != null) { a.setEnabled(selectionVisible); } a = kit.getActionByName(BaseKit.removeSelectionAction); if (a != null) { a.setEnabled(selectionVisible); } } } } ); } protected synchronized void modelChanged(BaseDocument oldDoc, BaseDocument newDoc) { if (oldDoc != null) { // remove all document layers drawLayerList.remove(oldDoc.getDrawLayerList()); } if (newDoc != null) { settingsChange(null); // add all document layers drawLayerList.add(newDoc.getDrawLayerList()); } } public void propertyChange(PropertyChangeEvent evt) { String propName = evt.getPropertyName(); if ("document".equals(propName)) { BaseDocument oldDoc = (evt.getOldValue() instanceof BaseDocument) ? (BaseDocument)evt.getOldValue() : null; BaseDocument newDoc = (evt.getNewValue() instanceof BaseDocument) ? (BaseDocument)evt.getNewValue() : null; modelChanged(oldDoc, newDoc); } else if ("margin".equals(propName)) { // NOI18N updateTextMargin(); } else if ("caret".equals(propName)) { // NOI18N if (evt.getOldValue() instanceof Caret) { ((Caret)evt.getOldValue()).removeChangeListener(this); } if (evt.getNewValue() instanceof Caret) { ((Caret)evt.getNewValue()).addChangeListener(this); } } else if ("enabled".equals(propName)) { // NOI18N if (!component.isEnabled()) { component.getCaret().setVisible(false); } } } protected Map createColoringMap() { Map coloringMap = SettingsUtil.getColoringMap(getKitClass(), (component == null)); // Test if there's a default coloring if (coloringMap.get(Settings.DEFAULT_COLORING) == null) { coloringMap.put(Settings.DEFAULT_COLORING, DefaultSettings.defaultColoring); } return coloringMap; } public Map getColoringMap() { if (coloringMap == null) { coloringMap = createColoringMap(); } return coloringMap; } public Coloring getDefaultColoring() { return (Coloring)getColoringMap().get(Settings.DEFAULT_COLORING); } public Coloring getColoring(String coloringName) { return (Coloring)getColoringMap().get(coloringName); } private Font getUniformFont() { Map cm = getColoringMap(); Iterator i = cm.entrySet().iterator(); List exclusionList = (List)Settings.getValue(getKitClass(), Settings.UNIFORM_FONT_EXCLUSION_LIST); Font uniFont = null; String uniFontName = null; int uniFontSize = 0; while (i.hasNext()) { Map.Entry me = (Map.Entry)i.next(); String coloringName = (String)me.getKey(); Coloring c = (Coloring)me.getValue(); if (c != null && (exclusionList == null || exclusionList.indexOf(coloringName) < 0)) { Font font = c.getFont(); if (font != null) { if (uniFont != null) { if (!font.getName().equals(uniFontName) || font.getSize() != uniFontSize) { return null; } } else { uniFont = font; uniFontName = uniFont.getName(); uniFontSize = uniFont.getSize(); } } } } return uniFont; } private int getMaximumFontHeight(Graphics g) { Map cm = getColoringMap(); Iterator i = cm.entrySet().iterator(); List exclusionList = (List)Settings.getValue(getKitClass(), Settings.UNIFORM_FONT_EXCLUSION_LIST); int maxHeight = 0; while (i.hasNext()) { Map.Entry me = (Map.Entry)i.next(); String coloringName = (String)me.getKey(); Coloring c = (Coloring)me.getValue(); if (c != null && (exclusionList == null || exclusionList.indexOf(coloringName) < 0)) { Font font = c.getFont(); if (font != null) { maxHeight = Math.max(maxHeight, FontMetricsCache.getFontMetrics(font, g).getHeight()); } } } return maxHeight; } protected void initFonts(Graphics g) { Class kitClass = Utilities.getKitClass(component); Insets m = (Insets)Settings.getValue(kitClass, Settings.MARGIN); if (kitClass != null && m != null) { component.setMargin(m); } // Apply the default coloring to the component getDefaultColoring().apply(component); if (renderingHints != null) { ((Graphics2D)g).setRenderingHints(renderingHints); } char[] chars = new char[] { 'i', 'W', 'm', ' ', 'm', 'm', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; Coloring dc = getDefaultColoring(); // Handle line number fonts and widths Coloring lnc = (Coloring)getColoringMap().get(Settings.LINE_NUMBER_COLORING); if (lnc != null) { Font lnFont = lnc.getFont(); if (lnFont == null) { lnFont = dc.getFont(); } FontMetrics lnFM = g.getFontMetrics(lnFont); int fs = lnFont.getStyle(); int maxWidth = 1; for (int i = 5; i < 15; i++) { maxWidth = Math.max(maxWidth, lnFM.charsWidth(chars, i, 1)); } lineNumberDigitWidth = maxWidth; lineNumberAscent = (int)(lnFM.getAscent() * lineHeightCorrection); } // Check whether the uniform font is used Font uniFont = getUniformFont(); if (uniFont != null) { int[] testStarts = new int[] { 0, 1, 2, 2, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; int[] testLens = new int[] { 1, 1, 1, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; int[][] results = new int[testStarts.length][]; Font[] fonts = new Font[STYLE_CNT]; FontMetrics[] fms = new FontMetrics[STYLE_CNT]; charHeight = 1; // important for settings changes for (int i = 0; i < STYLE_CNT; i++) { fonts[i] = new Font(uniFont.getName(), i, uniFont.getSize()); fms[i] = g.getFontMetrics(fonts[i]); charHeight = Math.max(charHeight, (int)(fms[i].getHeight() * lineHeightCorrection)); ascents[i] = (int)(fms[i].getAscent() * lineHeightCorrection); } for (int i = 0; i < testStarts.length; i++) { results[i] = new int[STYLE_CNT]; for (int j = 0; j < STYLE_CNT; j++) { // results[i][j] = (int)fonts[j].getStringBounds(chars, // testStarts[i], testLens[i], frc).getWidth(); results[i][j] = fms[j].charsWidth(chars, testStarts[i], testLens[i]); } } // Test whether fonts are superFixed or fixed boolean wantSuperFixed = true; boolean wantFixed = true; for (int i = 0; i < 3; i++) { for (int j = 0; j < STYLE_CNT; j++) { if (results[i][j] != results[0][j]) { wantFixed = false; } if (results[i][j] != results[i][0]) { wantSuperFixed = false; } } } if (!wantFixed) { wantSuperFixed = false; } fixedFont = wantFixed; superFixedFont = wantSuperFixed; // System.out.println("ExtUI.java:590 fixedFont=" + fixedFont + ", superFixedFont=" + superFixedFont); fixedFont = false; // !!! superFixedFont = false; // !!! // Assign spaceWidths[] if (fixedFont) { for (int i = 0; i < STYLE_CNT; i++) { spaceWidths[i] = results[0][i]; } } else { for (int i = 0; i < STYLE_CNT; i++) { spaceWidths[i] = results[3][i] - results[4][i]; } } for (int i = 0; i < STYLE_CNT; i++) { // System.out.println("ExtUI.java:366 numWidths[" + i + "]=" + numWidths[i]); // NOI18N } } else { // unifont is null // Get character height charHeight = getMaximumFontHeight(g); // compute space widths for the styles for (int i = 0; i < STYLE_CNT; i++) { spaceWidths[i] = charHeight / 3; } } // defaultSpaceWidth = spaceWidths[0]; // !!! could be computed somehow? charHeight = (int)(getMaximumFontHeight(g) * lineHeightCorrection); defaultSpaceWidth = FontMetricsCache.getFontMetrics(dc.getFont(), g).stringWidth(" "); for (int i = 0; i < STYLE_CNT; i++) { spaceWidths[i] = defaultSpaceWidth; } // Update various sizes fontsInited = true; if (component != null) { ((BaseTextUI)component.getUI()).updateHeight(); updateLineNumberWidth(); checkLineLimit(); } // Possibly display debugging info if (System.getProperty("netbeans.debug.editor.font") != null) { System.out.println("fixedFont=" + fixedFont + ", superFixedFont=" + superFixedFont); } /* JDK1.3 patch for the behavior that occurs when the line is wider * than the screen and the user first clicks End key to go to the end * and then goes back by (Ctrl+)Left. As the non-simple scrolling mode * is used in JViewport in 1.3 the line number block appears shifted * to the right and gets repainted after 300ms which looks ugly. * The patch is to set the simple scrolling mode into JViewport. * * getParentViewport().setScrollMode(0); // 2 stands for SIMPLE_SCROLL_MODE * */ try { JViewport vp = getParentViewport(); if (vp != null) { java.lang.reflect.Method setScrollModeMethod = JViewport.class.getDeclaredMethod( "setScrollMode", new Class[] { Integer.TYPE }); // NOI18N setScrollModeMethod.invoke(vp, new Object[] { new Integer(0) }); } } catch (Throwable t) { } } public final JTextComponent getComponent() { return component; } /** Get the document to work on. Either component's document or printed document * is returned. It can return null in case the component's document is not instance * of BaseDocument. */ public final BaseDocument getDocument() { return (component != null) ? Utilities.getDocument(component) : printDoc; } private Class getKitClass() { return (component != null) ? Utilities.getKitClass(component) : ((printDoc != null) ? printDoc.getKitClass() : null); } public Object getProperty(Object key) { return props.get(key); } public void putProperty(Object key, Object value) { Object oldValue; if (value != null) { oldValue = props.put(key, value); } else { oldValue = props.remove(key); } firePropertyChange(key.toString(), oldValue, value); } /** Create or get extended editor component */ public JComponent getExtComponent() { if (extComponent == null) { if (component != null) { setLineNumberEnabled(true); // enable line numbering // Extended component is a panel extComponent = new JPanel(new BorderLayout()); // Configure the scroll-pane with the component JScrollPane scroller = new JScrollPane(component); scroller.getViewport().setMinimumSize(new Dimension(4,4)); extComponent.add(scroller); // Install the status-bar panel into extComponent in extUI extComponent.add(getStatusBar().getPanel(), BorderLayout.SOUTH); } } return extComponent; } public Abbrev getAbbrev() { if (abbrev == null) { abbrev = new Abbrev(this, true, true); } return abbrev; } public WordMatch getWordMatch() { if (wordMatch == null) { wordMatch = new WordMatch(this); } return wordMatch; } public ToolTipSupport getToolTipSupport() { if (toolTipSupport == null) { toolTipSupport = new ToolTipSupport(this); } return toolTipSupport; } public StatusBar getStatusBar() { if (statusBar == null) { if (extComponent != null) { statusBar = new StatusBar(this); } } return statusBar; } final DrawLayerList getDrawLayerList() { return drawLayerList; } /** Find the layer with some layer name in the layer hierarchy */ public synchronized DrawLayer findLayer(String layerName) { return drawLayerList.findLayer(layerName); } /** Add new layer and use its priority to position it in the chain. * If there's the layer with same visibility then the inserted layer * will be placed after it. * * @param layer layer to insert into the chain */ public synchronized boolean addLayer(DrawLayer layer) { return drawLayerList.add(layer); } public synchronized DrawLayer removeLayer(String layerName) { return drawLayerList.remove(layerName); } public void showPopupMenu(int x, int y) { BaseKit kit = Utilities.getKit(component); if (kit != null) { Action a = kit.getActionByName( BaseKit.buildPopupMenuAction); if (a != null) { a.actionPerformed(new ActionEvent(component, 0, "")); // NOI18N } JPopupMenu pm = (JPopupMenu)getProperty(POPUP_MENU_PROPERTY); if (pm != null) { pm.show(component, x, y); } } } public void hidePopupMenu() { JPopupMenu pm = (JPopupMenu)getProperty(POPUP_MENU_PROPERTY); if (pm != null) { pm.setVisible(false); } } public void repaint(int startY) { repaint(startY, component.getHeight()); } public void repaint(int startY, int height) { if (height <= 0) { return; } int width = Math.max(component.getWidth(), 0); startY = Math.max(startY, 0); component.repaint(0, startY, width, height); } public void repaintPos(int pos) throws BadLocationException { repaintBlock(pos, pos); } public void repaintBlock(int startPos, int endPos) throws BadLocationException { BaseTextUI ui = (BaseTextUI)component.getUI(); if (startPos > endPos) { // swap int tmpPos = startPos; startPos = endPos; endPos = tmpPos; } try { int yFrom = ui.getYFromPos(startPos); int yTo = ui.getYFromPos(endPos); repaint(yFrom, (yTo - yFrom) + charHeight); } catch (BadLocationException e) { if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N e.printStackTrace(); } } } /** Is the parent of some editor component a viewport */ private JViewport getParentViewport() { Component pc = component.getParent(); return (pc instanceof JViewport) ? (JViewport)pc : null; } /** Finds the frame - parent of editor component */ public static Frame getParentFrame(Component c) { do { c = c.getParent(); if (c instanceof Frame) { return (Frame)c; } } while (c != null); return null; } /** Possibly update virtual width. If the width * is really updated, the method returns true. */ public boolean updateVirtualWidth(int width) { boolean updated = false; if (width > virtualSize.width) { int widthInc = componentSizeIncrement.width; widthInc = (widthInc < 0) ? (lastExtentBounds.width * (-widthInc) / 100) : widthInc * defaultSpaceWidth; virtualSize.width = width + widthInc; virtualSizeUpdated = true; updated = true; } return updated; } /** Possibly update virtual height. If the height * is really updated, the method returns true. There is * a slight difference against virtual width in that * if the height is shrinked too much the virtual height * is shrinked too. */ public boolean updateVirtualHeight(int height) { boolean updated = false; updateLineNumberWidth(); if (height > virtualSize.height) { int heightInc = componentSizeIncrement.height; heightInc = (heightInc < 0) ? (lastExtentBounds.height * (-heightInc) / 100) : heightInc * charHeight; virtualSize.height = height + heightInc; virtualSizeUpdated = true; updated = true; } if (height < virtualSize.height - lastExtentBounds.height) { virtualSize.height = height; virtualSizeUpdated = true; updated = true; } return updated; } public boolean isLineNumberEnabled() { return lineNumberEnabled; } public void setLineNumberEnabled(boolean lineNumberEnabled) { this.lineNumberEnabled = lineNumberEnabled; lineNumberVisible = lineNumberEnabled && lineNumberVisibleSetting; } public void updateLineNumberWidth() { int oldWidth = lineNumberWidth; if (lineNumberVisible) { try { BaseDocument doc = getDocument(); int lineCnt = Utilities.getLineOffset(doc, doc.getLength()) + 1; int digitCnt = Integer.toString(lineCnt).length(); if (digitCnt > lineNumberMaxDigitCnt) { lineNumberMaxDigitCnt = digitCnt; } } catch (BadLocationException e) { lineNumberMaxDigitCnt = 1; } lineNumberWidth = lineNumberMaxDigitCnt * lineNumberDigitWidth; if (lineNumberMargin != null) { lineNumberWidth += lineNumberMargin.left + lineNumberMargin.right; } } else { lineNumberWidth = 0; } updateTextMargin(); if (oldWidth != lineNumberWidth) { // changed if (component != null) { component.repaint(); } } } void checkLineLimit() { BaseDocument doc = getDocument(); if (doc != null) { Integer lineLimit = (Integer)doc.getProperty(BaseDocument.LINE_LIMIT_PROP); if (lineLimit != null) { updateVirtualWidth(spaceWidths[0] * lineLimit.intValue() + lineNumberWidth); } } } public void updateTextMargin() { Insets orig = textMargin; Insets cm = (component != null) ? component.getMargin() : null; if (cm != null) { textMargin = new Insets(cm.top, cm.left + lineNumberWidth, cm.bottom, cm.right); } else { textMargin = new Insets(0, lineNumberWidth, 0, 0); } if (orig.top != textMargin.top || orig.bottom != textMargin.bottom) { ((BaseTextUI)component.getUI()).invalidateStartY(); } } public Rectangle getExtentBounds() { return getExtentBounds(null); } /** Get position of the component extent. The (x, y) are set to (0, 0) if there's * no viewport or (-x, -y) if there's one. */ public Rectangle getExtentBounds(Rectangle r) { if (r == null) { r = new Rectangle(); } if (component != null) { JViewport port = getParentViewport(); if (port != null) { Point p = port.getViewPosition(); r.width = port.getWidth(); r.height = port.getHeight(); r.x = p.x; r.y = p.y; } else { // no viewport r.width = component.getWidth(); r.height = component.getHeight(); r.x = 0; r.y = 0; } } return r; } /** Get the begining of the area covered by text */ public final Insets getTextMargin() { return new Insets(textMargin.top, textMargin.left, textMargin.bottom, textMargin.right); } public void scrollRectToVisible(final Rectangle r, final int scrollPolicy) { Utilities.runInEventDispatchThread( new Runnable() { public void run() { scrollRectToVisibleFragile(r, scrollPolicy); } } ); } /** Must be called with EventDispatchThread */ boolean scrollRectToVisibleFragile(Rectangle r, int scrollPolicy) { Insets margin = getTextMargin(); Rectangle bounds = getExtentBounds(); r = new Rectangle(r); // make copy of orig rect r.x -= margin.left; r.y -= margin.top; bounds.width -= margin.left + margin.right; bounds.height -= margin.top + margin.bottom; return scrollRectToVisibleImpl(r, scrollPolicy, bounds); } /** Scroll the view so that requested rectangle is best visible. * There are different scroll policies available. */ private boolean scrollRectToVisibleImpl(Rectangle r, int scrollPolicy, Rectangle bounds) { if (bounds.width <= 0 || bounds.height <= 0) { return false; } // handle find scrolling specifically if (scrollPolicy == SCROLL_FIND) { int nx = Math.max(r.x - scrollFindInsets.left, 0); r.width += (r.x - nx) + scrollFindInsets.right; r.x = nx; int ny = Math.max(r.y - scrollFindInsets.top, 0); r.height += (r.y - ny) + scrollFindInsets.bottom; r.y = ny; return scrollRectToVisibleImpl(r, SCROLL_SMALLEST, bounds); // recall } // r must be within virtualSize's width if (r.x + r.width > virtualSize.width) { r.x = virtualSize.width - r.width; if (r.x < 0) { r.x = 0; r.width = virtualSize.width; } return scrollRectToVisibleImpl(r, scrollPolicy, bounds); // recall } // r must be within virtualSize's height if (r.y + r.height > virtualSize.height) { r.y = virtualSize.height - r.height; if (r.y < 0) { r.y = 0; r.height = virtualSize.height; } return scrollRectToVisibleImpl(r, scrollPolicy, bounds); } // if r extends bounds dimension it must be corrected now if (r.width > bounds.width || r.height > bounds.height) { Rectangle caretRect = new Rectangle((Rectangle)component.getCaret()); if (caretRect.x >= r.x && caretRect.x + caretRect.width <= r.x + r.width && caretRect.y >= r.y && caretRect.y + caretRect.height <= r.y + r.height ) { // caret inside requested rect // move scroll rect for best caret visibility int overX = r.width - bounds.width; int overY = r.height - bounds.height; if (overX > 0) { r.x -= overX * (caretRect.x - r.x) / r.width; } if (overY > 0) { r.y -= overY * (caretRect.y - r.y) / r.height; } } r.height = bounds.height; r.width = bounds.width; // could be different algorithm return scrollRectToVisibleImpl(r, scrollPolicy, bounds); } int newX = bounds.x; int newY = bounds.y; boolean move = false; // now the scroll rect is within bounds of the component // and can have size of the extent at maximum if (r.x < bounds.x) { move = true; switch (scrollPolicy) { case SCROLL_MOVE: newX = (scrollJumpInsets.left < 0) ? (bounds.width * (-scrollJumpInsets.left) / 100) : scrollJumpInsets.left * defaultSpaceWidth; newX = Math.min(newX, bounds.x + bounds.width - (r.x + r.width)); newX = Math.max(r.x - newX, 0); // new bounds.x break; case SCROLL_DEFAULT: case SCROLL_SMALLEST: default: newX = r.x; break; } updateVirtualWidth(newX + bounds.width); } else if (r.x + r.width > bounds.x + bounds.width) { move = true; switch (scrollPolicy) { case SCROLL_SMALLEST: newX = r.x + r.width - bounds.width; break; default: newX = (scrollJumpInsets.right < 0) ? (bounds.width * (-scrollJumpInsets.right) / 100 ) : scrollJumpInsets.right * defaultSpaceWidth; newX = Math.min(newX, bounds.width - r.width); newX = (r.x + r.width) + newX - bounds.width; break; } updateVirtualWidth(newX + bounds.width); } if (r.y < bounds.y) { move = true; switch (scrollPolicy) { case SCROLL_MOVE: newY = r.y; newY -= (scrollJumpInsets.top < 0) ? (bounds.height * (-scrollJumpInsets.top) / 100 ) : scrollJumpInsets.top * charHeight; break; case SCROLL_SMALLEST: newY = r.y; break; case SCROLL_DEFAULT: default: newY = r.y - (bounds.height - r.height) / 2; // center break; } newY = Math.max(newY, 0); } else if (r.y + r.height > bounds.y + bounds.height) { move = true; switch (scrollPolicy) { case SCROLL_MOVE: newY = (r.y + r.height) - bounds.height; newY += (scrollJumpInsets.bottom < 0) ? (bounds.height * (-scrollJumpInsets.bottom) / 100 ) : scrollJumpInsets.bottom * charHeight; break; case SCROLL_SMALLEST: newY = (r.y + r.height) - bounds.height; break; case SCROLL_DEFAULT: default: newY = r.y - (bounds.height - r.height) / 2; // center break; } newY = Math.max(newY, 0); } if (move) { setExtentPosition(newX, newY); } return move; } void setExtentPosition(int x, int y) { JViewport port = getParentViewport(); if (port != null) { Point p = new Point(Math.max(x, 0), Math.max(y, 0)); port.setViewPosition(p); } } public void adjustWindow(int caretPercentFromWindowTop) { final Rectangle bounds = getExtentBounds(); if (component != null && (component.getCaret() instanceof Rectangle)) { Rectangle caretRect = (Rectangle)component.getCaret(); bounds.y = caretRect.y - (caretPercentFromWindowTop * bounds.height) / 100 + (caretPercentFromWindowTop * charHeight) / 100; Utilities.runInEventDispatchThread( new Runnable() { public void run() { scrollRectToVisible(bounds, SCROLL_SMALLEST); } } ); } } /** Set the dot according to the currently visible screen window. * #param percentFromWindowTop percentage giving the distance of the caret * from the top of the currently visible window. */ public void adjustCaret(int percentFromWindowTop) { JTextComponent c = component; if (c != null) { ExtUI extUI = Utilities.getExtUI(c); Rectangle bounds = extUI.getExtentBounds(); bounds.y += (percentFromWindowTop * bounds.height) / 100 - (percentFromWindowTop * extUI.charHeight) / 100; try { int pos = ((BaseTextUI)c.getUI()).getPosFromY(bounds.y); if (pos >= 0) { caretSetDot(pos, null, SCROLL_SMALLEST); } } catch (BadLocationException e) { } } } public void caretSetDot(int pos, Rectangle scrollRect, int scrollPolicy) { if (component != null) { Caret caret = component.getCaret(); if (caret instanceof BaseCaret) { ((BaseCaret)caret).setDot(pos, scrollRect, scrollPolicy); } else { caret.setDot(pos); } } } public void caretMoveDot(int pos, Rectangle scrollRect, int scrollPolicy) { if (component != null) { Caret caret = component.getCaret(); if (caret instanceof BaseCaret) { ((BaseCaret)caret).moveDot(pos, scrollRect, scrollPolicy); } else { caret.moveDot(pos); } } } /** This method is called by textui to do the paint. * It is forwarded either to paint through the image * and then copy the image area to the screen or to * paint directly to this graphics. The real work occurs * in Drawer. */ protected void paint(Graphics g) { if (component != null) { // component must be installed if (!fontsInited && g != null) { initFonts(g); getExtentBounds(lastExtentBounds); } ((BaseTextUI)component.getUI()).paintRegion(g); } } } /* * Log * 56 Gandalf-post-FCS1.48.1.6 4/18/00 Miloslav Metelka font height computing * fix * 55 Gandalf-post-FCS1.48.1.5 4/17/00 Miloslav Metelka printing fixed * 54 Gandalf-post-FCS1.48.1.4 4/5/00 Miloslav Metelka status bar patch2 * 53 Gandalf-post-FCS1.48.1.3 4/5/00 Miloslav Metelka getExtComponent() patch * 52 Gandalf-post-FCS1.48.1.2 3/9/00 Miloslav Metelka ensure settingsChange() * is called * 51 Gandalf-post-FCS1.48.1.1 3/9/00 Miloslav Metelka removed debug msg * 50 Gandalf-post-FCS1.48.1.0 3/8/00 Miloslav Metelka * 49 Gandalf 1.48 3/8/00 Miloslav Metelka project creating dedlok * fix * 48 Gandalf 1.47 2/18/00 Miloslav Metelka #5766 fix * 47 Gandalf 1.46 2/16/00 Miloslav Metelka obtaining font height * fixed * 46 Gandalf 1.45 1/26/00 Miloslav Metelka default coloring * applying moved * 45 Gandalf 1.44 1/16/00 Miloslav Metelka * 44 Gandalf 1.43 1/15/00 Miloslav Metelka * 43 Gandalf 1.42 1/14/00 Miloslav Metelka * 42 Gandalf 1.41 1/13/00 Miloslav Metelka * 41 Gandalf 1.40 1/10/00 Miloslav Metelka * 40 Gandalf 1.39 1/6/00 Miloslav Metelka * 39 Gandalf 1.38 1/4/00 Miloslav Metelka * 38 Gandalf 1.37 12/28/99 Miloslav Metelka * 37 Gandalf 1.36 11/24/99 Miloslav Metelka * 36 Gandalf 1.35 11/14/99 Miloslav Metelka * 35 Gandalf 1.34 11/11/99 Miloslav Metelka * 34 Gandalf 1.33 11/10/99 Miloslav Metelka * 33 Gandalf 1.32 11/8/99 Miloslav Metelka * 32 Gandalf 1.31 10/23/99 Ian Formanek NO SEMANTIC CHANGE - Sun * Microsystems Copyright in File Comment * 31 Gandalf 1.30 10/10/99 Miloslav Metelka * 30 Gandalf 1.29 10/8/99 Miloslav Metelka stability improvements * 29 Gandalf 1.28 10/6/99 Miloslav Metelka * 28 Gandalf 1.27 10/4/99 Miloslav Metelka * 27 Gandalf 1.26 9/30/99 Miloslav Metelka * 26 Gandalf 1.25 9/16/99 Miloslav Metelka * 25 Gandalf 1.24 9/15/99 Miloslav Metelka * 24 Gandalf 1.23 9/10/99 Miloslav Metelka * 23 Gandalf 1.22 8/20/99 Miloslav Metelka * 22 Gandalf 1.21 8/19/99 Miloslav Metelka automatic view shrink * 21 Gandalf 1.20 8/19/99 Miloslav Metelka printing line nums * 20 Gandalf 1.19 8/17/99 Miloslav Metelka * 19 Gandalf 1.18 8/9/99 Miloslav Metelka ClassCasts errors for * editor kit * 18 Gandalf 1.17 7/29/99 Miloslav Metelka * 17 Gandalf 1.16 7/26/99 Miloslav Metelka * 16 Gandalf 1.15 7/22/99 Miloslav Metelka * 15 Gandalf 1.14 7/21/99 Miloslav Metelka * 14 Gandalf 1.13 7/20/99 Miloslav Metelka * 13 Gandalf 1.12 7/9/99 Miloslav Metelka * 12 Gandalf 1.11 7/2/99 Miloslav Metelka * 11 Gandalf 1.10 6/29/99 Miloslav Metelka Scrolling and patches * 10 Gandalf 1.9 6/25/99 Miloslav Metelka from floats back to ints * 9 Gandalf 1.8 6/24/99 Miloslav Metelka Drawing improved * 8 Gandalf 1.7 6/22/99 Miloslav Metelka * 7 Gandalf 1.6 6/8/99 Miloslav Metelka * 6 Gandalf 1.5 6/1/99 Miloslav Metelka * 5 Gandalf 1.4 5/18/99 Miloslav Metelka patched printing * 4 Gandalf 1.3 5/15/99 Miloslav Metelka fixes * 3 Gandalf 1.2 5/13/99 Miloslav Metelka * 2 Gandalf 1.1 5/7/99 Miloslav Metelka line numbering and fixes * 1 Gandalf 1.0 5/5/99 Miloslav Metelka * $ */